// Leon Yu's Utility Extensions (YUX)
// A <SELECT> like menu, implemented using HTML and CSS for more stylistic control.

// Define yux object
if (typeof yux === 'undefined') var yux = {};

/*
Usage Example: 

var obj = new yux.SelectMenu('container');
obj.add('test1', function () {alert(1);}, {title : 'tooltip'});
obj.add('test2', function () {alert(2);}, {title : 'tooltip'}, 0);
obj.add('test3', function () {alert(3);}, {title : 'tooltip'}, 10);
obj.setPlaceholderText('no text');
*/

yux.SelectMenu = function (parameter)
{
	// if the browser does not support W3C DOM Level 1, this script would not work.
	// if (!document.createTextNode) return;

	// parent node
	if (typeof parameter === 'string')
	{
		// parameter is a string, assume it is element ID
		this.containerNode = document.getElementById(parameter);
	}
	else if (typeof parameter === 'object' && parameter.nodeType && parameter.nodeType === 1)
	{
		// parameter is already an element node
		this.containerNode = parameter;
	}
	else
	{
		return;
		// throw new Error('SelectMenu: Constructor must be invoked with a node or ID as parameter.');
	}

	if (this.containerNode === null)
	{
		return;
		throw new Error('SelectMenu constructor: Cannot get parent node. Please make sure called parameter is valid.');
	}

	// attach onclick event
	// onclick handling for this menu is simple enough that we can easily implement one function for all the menu items. 
	if (this.containerNode.addEventListener) this.containerNode.addEventListener('click', this.generateClickHander(this), false);
	else if (this.containerNode.attachEvent) this.containerNode.attachEvent('onclick', this.generateClickHander(this));
	else this.containerNode.onclick = this.generateClickHander(this);

	// placeholder when the list is empty
	this.placeholder = document.createElement('div');
	this.placeholder.style.fontStyle = 'italic';
	this.placeholder.style.visiblity = 'visible';
	this.placeholder.appendChild(document.createTextNode('(empty)'));
	this.containerNode.appendChild(this.placeholder);

	// initialize arrays. Cannot be done in the prototype, because that would simply be passed by reference when instantiated
	this.itemNodes = [];
	this.callback = [];
}

yux.SelectMenu.prototype =
{
	containerNode : null, // node
	itemNodes : null, // array
	callback : null, // array
	selectedIndex : -1, // number
	placeholder : null, // node

	add : function (text, callback, attributes, index)
	{
		// create node in DOM tree
		var NewItem = document.createElement('div');
		NewItem.className = 'SelectableItem';
		NewItem.appendChild(document.createTextNode(text));

		// assign additional attributes specified by user
		if (attributes)
		{
			for (var key in attributes)
				NewItem[key] = attributes[key];
		}

		// remove placeholder
		this.placeholder.style.display = 'none';

		// add to options list
		// insertion
		if (typeof index === 'number' && index <= this.itemNodes.length)
		{
			if (index < 0) index = 0;

			// add to document
			this.containerNode.insertBefore(NewItem, this.itemNodes[index]);

			// add node and callback to object array
			if (Array.prototype.splice)
			{
				this.itemNodes.splice(index, 0, NewItem);
				this.callback.splice(index, 0, callback);
			}
			else
			{
				this.itemNodes = this.itemNodes.slice(0, index).concat([NewItem], this.itemNodes.slice(index));
				this.callback = this.callback.slice(0, index).concat([callback], this.callback.slice(index));
			}

			// enumerate index
			for (var i = 0; i < this.itemNodes.length; i++)
				this.itemNodes[i].index = i;
		}
		// append
		else
		{
			// add to document
			this.containerNode.appendChild(NewItem);

			// enumerate index
			NewItem.index = this.itemNodes.length;

			// add node and callback to object array
			this.itemNodes[this.itemNodes.length] = NewItem;
			this.callback[this.callback.length] = callback;
		}

		// Proprietary CSS / CSS hack
		// Done in JavaScript instead of CSS to prevent compatibility issue in certain browsers (e.g. IE8)
		if (NewItem.currentStyle)
		{
			if (NewItem.currentStyle.cursor != 'pointer')
				NewItem.style.cursor = 'hand';
		}
	},

	// remove one item
	remove : function (index)
	{
		// remove node in DOM tree
		this.containerNode.removeChild(this.itemNodes[index]);

		if (Array.prototype.splice)
		{
			this.itemNodes.splice(index, 1);
			this.callback.splice(index, 1);
		}
		else
		{
			this.itemNodes = this.itemNodes.slice(0, index).concat(this.itemNodes.slice(index + 1));
			this.callback = this.callback.slice(0, index).concat(this.callback.slice(index + 1));
		}

		if (this.selectedIndex === index) this.selectedIndex = -1;
	},

	// remove all items
	removeAll : function ()
	{
		// remove from DOM tree
		for (var i = 0; i < this.itemNodes.length; i++)
			this.containerNode.removeChild(this.itemNodes[i]);

		// empty objects
		this.itemNodes = [];
		this.callback = [];

		this.selectedIndex = -1;
		this.placeholder.style.display = ''; // show placeholder
	},

	// remove selection
	removeSelection : function ()
	{
		if (this.selectedIndex === -1) return;

		this.remove(this.selectedIndex);
		this.selectedIndex = -1;
	},

	// mark selection, call callback
	select : function (index)
	{
		// if it is already select, we do not need to do anything
		// if (this.selectedIndex === index) return;

		// unmark previous selection
		this.clearSelection();

		// mark selection
		this.itemNodes[index].className = 'SelectedItem';
		this.selectedIndex = index;

		// evoke callback
		this.callback[index]();
	},

	// clear any selection
	clearSelection : function ()
	{
		if (this.selectedIndex === -1) return;

		this.itemNodes[this.selectedIndex].className = 'SelectableItem';
		this.selectedIndex = -1;
	},

	// a generator function is used instead of inline anonymous function so there is a less of a chance of memory leak.
	generateClickHander : function (obj)
	{
		return function (evt)
		{
			// grab event object for IE
			if (!evt) var evt = window.event;

			// cross-browser target
			if (evt.target) var target = evt.target;
			else if (evt.srcElement) var target = evt.srcElement;
			else var target = this;

			// For earlier Gecko version (including Gecko 1.0) 
			// Early Gecko actually yield text nodes
			while (target.nodeType !== 1) 
				target = target.parentNode;

			if (typeof target.index === 'number')
				obj.select(target.index);
		}
	},

	// change the placeholder text when no item is displayed
	setPlaceholderText : function (text)
	{
		this.placeholder.firstChild.nodeValue = text;
	}
}
